
"""
Provides the Action base class. Actual classes for actions are kept 
elsewhere.

An action is something that happens in the game. It can be a weapon being
fired, a person moving, treasure being found, cargo being sold, etc. 
Actions, unlike events, don't need to be processed immediately, though. 
They only need to be processed sooner than later. Some Event handlers 
might opt to create Actions to deal with them later.
"""

from utils import Identified

class Action(Identified):
    #Differentiation from other objects
    ##NOTE: Official code should prefer isinstance(). Alert authors when that
    ##    doesnt' work.
    name = ""
    description = ""
    
    #References to other objects
    actors = {}          #type -> List of actors of that type
    game = None
    
    #Limitations
    thingsNeeded = {}    #type -> number of actors needed
    thingsMax = {}       #type -> numbers of actors usable. 0 means no max.
    turnsNeeded = 1
    turnsMax = -1
    
    #Status
    turnsActing = 0
    isActing = False
    
    def __init__(self, game, actors, **kwargs):
        """
        
        Arguments:
        game: The game this action lives in
        actors: A dictionary that maps an actor to the type of actor it is.
        """
        super(Action, self).__init__(**kwargs)
        self.game = game
        #This part is to initialize self.actors so we don't do the same
        #pointless thing again and again in child classes.
        for t, n in self.thingsNeeded:
            self.actors[t] = []
        for a, t in actors:
            self.addActor(a, t)
    
    def validate(self):
        assert self.game is not None
        assert self.name is not None and self.name != ""
        assert self.description is not None
        assert len(self.thingsNeeded) > 0
        assert self.thingsNeeded.keys() == self.actors.keys()
        assert turnsNeeded > 0
        #TODO: assert all our actors know they are registered with us
    
    def hasActors(self):
        for type_, n in self.thingsNeeded:
            if len(self.actors[type_]) < n:
                return False
        return True
    
    def addActor(self, actor, type_):
        """Add an actor to this action.
        
        addActor() will raise a ValueError if the actor can't be used
        for whatever reason. It will also tell the actor to set its
        action to this one.
        """
        if not type_ in self.actors.keys():
            raise ValueError("Given actor type unusable for this action")
        if actor in self.actors[type_]:
            """This doesn't raise an error because it stops some
            programming errors."""
            return
        if self.thingsMax[type_] > 0 and len(self.actors[type_]) >= self.thingsMax[type_]:
            raise ValueError("This action cannot use more actors of that type.")
        for t, l in self.actors:
            if t is not type_ and actor in l:
               raise ValueError("Actor already in action as different type")
        self.actors[type_].append(actor)
        actor.setAction(self)
    
    def canStart(self):
        """Check if this action can be started.
        
        By default, canStart() checks that the action isn't acting, and
        that the action has enough actors.
        """
        return (self.isActing is False) and self.hasActors()
    
    def canStop(self):
        """Check if this action can be stopped.
        
        By default, canStop() checks that the action is acting and that
        it has run for enough turns to be completed.
        """
        return self.isActing and self.turnsActing > self.turnsNeeded
    
    def canContinue(self):
        """Check if this action can be continued.
        
        By default, checks if this action has been running for a number
        of turns less than or equal to the maximum possible, if a max
        is provided. Additionally, checks if the action is acting.
        """
        turns = (self.turnsMax == -1 or self.turnsActing <= self.turnsMax)
        return self.isActing and turns
    
    def canRelease(self, thing):
        """Can this action release the specified thing and still continue?"""
        #1: Find type of thing
        type_ = None
        for t, l in self.actors:
            if thing in l:
                type_ = t
                break
        else:
            return True
        #2: Check lengths
        if type_ is not None:
            if self.thingsNeeded[type_] < len(self.actors[type_]):
                return True
            else:
                return False
        else:
            return True
    
    def startActing(self):
        """Starts an action, and executes the action once for the current turn.
        
        Can raise an AssertionError if the action can't start. Use 
        canStart() to check for that manually.
        """
        assert self.canStart(), "Action %s (%s) can't start." % (self.name,
                                                                 self.id)
        self.isActing = True
        self.turnsActing = 0
        self.act()
        
    def continueActing(self):
        """Executes the action once for the current turn.
        
        Can raise an AssertionError if the action can't continue. Use
        canContinue() to check for that manually.
        """
        assert self.canContinue(), "Action %s (%s) can't continue." % \
                                   (self.name, self.id)
        self.turnsActing += 1
        self.act()
    
    def stopActing(self):
        """Stops the action from executing.
        
        Can raise an AssertionError if the action can't stop. Use 
        canStop() to check for that manually.
        """
        assert self.canStop(), "Action %s (%s) can't stop." % (self.name,
                                                               self.id)
        self.isActing = False
    
    def act():
        """Do this action.
        
        WARNING: This is called by the startActing() and 
        continueActing() methods. Don't mix the two sets unless you
        know what you're doing.
        """
        raise NotImplementedError
    
